//
//  provapipeprocessi.c
//  
//
//  Created by Alessandro Bevilacqua on 22/05/12.
//  Copyright 2012 __MyCompanyName__. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <locale.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <execinfo.h>

#define PIPE_DESCRIPTOR fd
#define PIPE_READ_END PIPE_DESCRIPTOR[0]
#define PIPE_WRITE_END PIPE_DESCRIPTOR[1]

const int NBYTE = 500;
const int MAX_STARTABLE_PROCESSES = 20;
const int LOG_STRING_MAXLENGHT = 250;

void currentformattedtime(char *);
int putlog(char *, char *);
void nomefileout(char* , char* , char* );
int roundit(double);
int withinrange(int value, double oldmin, double oldmax, double newmin, double newmax);

void handler(int sig) {
    void *array[10];
    size_t size;
    
    // get void*'s for all entries on the stack
    size = backtrace(array, 10);
    
    // print out all the frames to stderr
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(array, size, 2);
    exit(1);
}

int main(int argc, char** argv) {
    signal(SIGSEGV, handler);
    if (argc == 1) {
        printf("Errore. Inserire almeno un argomento per il corretto funzionamento. Esempio: %s pwd\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    char buffer[NBYTE], logstring[LOG_STRING_MAXLENGHT], processtostart[20], stringanomefileout[50], thisprogramnametrimmed[20], LOGFILE_DIR_ADDRESS[50] = "./d";
    int PIPE_DESCRIPTOR[2], pid, i=1, pidvector[MAX_STARTABLE_PROCESSES], k, j, randomdata, randomint, totalstartedprocesses = 0;
    if (argv[0][0] == '.' && argv[0][1] == '/') {
        for (j=0; j<20; j++) {
            thisprogramnametrimmed[j] = argv[0][j+2];
            if (argv[0][j+2] == '\0')
                break;
        }
    }
    else {
        strcpy(thisprogramnametrimmed, argv[0]);
    }
    strcat(LOGFILE_DIR_ADDRESS, thisprogramnametrimmed);
    printf("%s\n", LOGFILE_DIR_ADDRESS);
    struct stat st;
    if(stat(LOGFILE_DIR_ADDRESS,&st) == 0) {
        // La directory e' gia' esistente
    }
    else {
        // creo la directory per il log. L'unico errore che ammetto e' EEXIST, ovvero la directory e' gia' esistente
        if (mkdir(LOGFILE_DIR_ADDRESS, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) {
            if (errno != EEXIST) {
                printf("Errore nella creazione della directory per il log.\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    FILE *outfile;
    // dichiaro e inizializzo la stringa LOGFILE_ADDRESS a ./argv[0].log
    char LOGFILE_ADDRESS[50];
    strcpy(LOGFILE_ADDRESS, LOGFILE_DIR_ADDRESS);
    strcat(LOGFILE_ADDRESS, "/");
    strcat(LOGFILE_ADDRESS, thisprogramnametrimmed);
    strcat(LOGFILE_ADDRESS, ".log");
    pipe(PIPE_DESCRIPTOR);
    while ((i<argc) && (i<=MAX_STARTABLE_PROCESSES+1)) {
        strcpy(processtostart, argv[i]);
        pid = fork();
        if (pid == -1) {
            sprintf(logstring, "Errore nella fork per il processo %s. Errore %d: %s", argv[i], errno, strerror(errno));
            putlog(LOGFILE_ADDRESS, logstring); 
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) {                                // inizio codice figlio
            i = argc;
            sprintf(logstring, "Avvio del processo %s, process id: %d", processtostart, getpid());
            putlog(LOGFILE_ADDRESS, logstring);
            close(PIPE_READ_END);                           // chiudo la pipe in lettura
            kill(getpid(), SIGSTOP);
            sleep(1);
            dup2(PIPE_WRITE_END, STDOUT_FILENO);            // redirigo stdout sulla write end della pipe
            execlp(processtostart, processtostart, NULL);   // eseguo il comando specificato
        }
        else {
            pidvector[i-1] = pid;
            totalstartedprocesses++;
            i++;
        }
    }                                                       // continuo con codice del padre
    close(PIPE_WRITE_END);                                  // chiudo la pipe in scrittura
    fcntl(PIPE_READ_END, F_SETFL, O_NONBLOCK);              // con la read bloccante tendeva a bloccarsi
                                                            // e non ripartire piu'
    randomdata = open("/dev/random", O_RDONLY);
    for (i = 0; i<totalstartedprocesses; i++) {
        k = 0;
        do {
        read(randomdata, &randomint, 1);
        randomint = withinrange(randomint, (double) 0, (double) 255, (double) 0, (double) (totalstartedprocesses-1));
        } while (pidvector[randomint] == 0);
        if (kill(pidvector[randomint], SIGCONT) == -1) {
            sprintf(logstring, "Errore invio segnale al processo %d. Errore %d: %s", pidvector[randomint], errno, strerror(errno));
            putlog(LOGFILE_ADDRESS, logstring);
        }
        else {
            nomefileout(stringanomefileout, argv[0], argv[randomint+1]);
            outfile = fopen(stringanomefileout, "w");
            sprintf(logstring, "Processo %d:%s attivato", pidvector[randomint], argv[randomint+1]);
            putlog(LOGFILE_ADDRESS, logstring);
            sleep(2);
            while ((k = read(PIPE_READ_END, buffer, NBYTE)) > 0) {        
                buffer[k] = '\0';
                fprintf(outfile, "%s", buffer);                 
            }
            fclose(outfile);
            // ora controllo per massimo 10 volte se il figlio ha terminato correttamente
            j = 0;
            do {
                k = waitpid(pidvector[randomint], NULL, WNOHANG);
                j++;
            } while ((k != pidvector[randomint]) && (j<=20));
            // se il figlio non ha ancora terminato lo uccido esplicitamente
            if (k != pidvector[randomint]) {
                kill(pidvector[randomint], SIGKILL);
                sprintf(logstring, "Il processo %d non ha terminato correttamente. E' stato terminato in modo forzato", pidvector[randomint]);
                putlog(LOGFILE_ADDRESS, logstring);
            }
            else {
                sprintf(logstring, "Il processo %d ha terminato correttamente. L'output e' stato scritto nel file %s", pidvector[randomint], stringanomefileout);
                putlog(LOGFILE_ADDRESS, logstring);
            }
            // pongo (pidvector[randomint] = 0) per via del ciclo while che sceglie casualmente il prossimo figlio da
            // avviare, in questo modo riesco a ripetere "l'estrazione" quando esce un figlio che è gia' stato gestito
            pidvector[randomint] = 0;            
        }
    }
    close(randomdata);
    return 0;
}

void currentformattedtime(char *str) {
    int dim;
    time_t t = time(NULL);
    struct tm *tp = localtime(&t);
    setlocale(LC_ALL, "");
    dim = strftime(str, 26, "%x - %X", tp);
    if (dim == 0) {
        printf("Errore data\n");
        exit(EXIT_FAILURE);
    }
}

int putlog(char *destination, char *string) {    
    char stringaoradata[30];
    char tologfile[LOG_STRING_MAXLENGHT];
    FILE *logfile = fopen(destination, "r");
    if (logfile == NULL) {
        logfile = fopen(destination, "w");
    }
    else {
        fclose(logfile);
        logfile = fopen(destination, "a");
    }
    currentformattedtime(stringaoradata);
    sprintf(tologfile, "[%s]: %s\n", stringaoradata, string);
    fprintf(logfile, "%s", tologfile);
    fclose(logfile);
    return 0;
}

void nomefileout(char* stringanomefile, char* comando, char* comandoavviato) {
    int dim;
    time_t t = time(NULL);
    struct tm *tp = localtime(&t);
    setlocale(LC_ALL, "");
    dim = sprintf(stringanomefile, "%s.%s.%d.%d.%d.%d.%d.%d.out", comando, comandoavviato, tp->tm_hour, tp->tm_min, tp->tm_sec, tp->tm_mday, (tp->tm_mon + 1), (tp->tm_year)+1900);
    if (dim < 0) {
        printf("Errore data\n");
        exit(EXIT_FAILURE);
    }
}

int roundit(double roundme) {
    double decimalpart = (double)(roundme - (int) roundme);
    if (decimalpart < 0.5) 
        return (int) roundme;
    else
        return ((int) roundme + 1);
}

int withinrange(int value, double oldmin, double oldmax, double newmin, double newmax) {
    double newvalue = (((value - oldmin) * (newmax - newmin)) / (oldmax - oldmin)) + newmin;
    return roundit(newvalue);
}
